<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>D3.js Line Chart</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>

    <style>
        .line {
            fill: none;
            stroke: steelblue;
            stroke-width: 1px;
        }
    </style>
</head>

<body>
    <h1>GPS和加速度示例</h1>
    <p id="gps">GPS位置：等待数据...</p>
    <p id="acceleration">加速度：等待数据...</p>
    <button id="requestPermissionBtn" style="display:none;">请求传感器权限</button>

    <svg id="chartA" width="400" height="200"></svg>
    <svg id="chartB" width="400" height="200"></svg>
    <svg id="chartC" width="400" height="200"></svg>

    <script>

        class Chart {
            constructor(svg) {
                this.data = [];
                this.svg = svg;
                this.margin = { top: 20, right: 30, bottom: 30, left: 40 };
                this.width = this.svg.attr("width") - this.margin.left - this.margin.right;
                this.height = this.svg.attr("height") - this.margin.top - this.margin.bottom;
                this.g = this.svg.append("g").attr("transform", `translate(${this.margin.left},${this.margin.top})`);

                this.x = d3.scaleLinear()
                    .domain([0, 1])
                    .range([0, this.width]);

                this.y = d3.scaleLinear()
                    .domain([0, 1])
                    .range([this.height, 0]);

                this.xAxis = this.g.append("g")
                    .attr("transform", `translate(0,${this.height})`)
                    .call(d3.axisBottom(this.x));

                this.yAxis = this.g.append("g")
                    .call(d3.axisLeft(this.y));

                this.line = d3.line()
                    .x(d => this.x(d.time))
                    .y(d => this.y(d.value));

                this.path = this.g.append("path")
                    .datum(this.data)
                    .attr("class", "line")
                    .attr("d", this.line);
            }

            min = 0;
            max = 0;
            startTime = Date.now() / 1000;
            pushData(value, duration = 60) {
                const now = Date.now() / 1000 - this.startTime;

                const i = this.data.findIndex(d => d.time >= now - duration);
                this.data.splice(0, i);
                this.x.domain([now - duration, now]);
                this.xAxis.call(d3.axisBottom(this.x));

                if (value < this.min || value > this.max) {
                    (value < this.min) && (this.min = value) || (this.max = value);
                    this.y.domain([this.min, this.max]);
                    this.yAxis.call(d3.axisLeft(this.y));
                }

                this.data.push({ time: now, value });
                this.path.datum(this.data)
                    .attr("d", this.line);
            }
        }

        function main() {
            if (!(navigator.geolocation && window.DeviceMotionEvent && window.DeviceOrientationEvent)) {
                alert("此设备或浏览器不支持地理定位或设备运动事件。");
                return;
            }

            const chartA = new Chart(d3.select('#chartA'));
            const chartB = new Chart(d3.select('#chartB'));
            const chartC = new Chart(d3.select('#chartC'));

            initDeviceMotion();

            navigator.geolocation.watchPosition(
                gpsSuccessCallback,
                gpsErrorCallback,
                { enableHighAccuracy: true });

            function initDeviceMotion() {
                if (typeof DeviceMotionEvent.requestPermission === 'function') {
                    // iOS 13+ 需要请求传感器权限
                    let btn = document.getElementById('requestPermissionBtn');
                    btn.style.display = 'block';
                    btn.addEventListener('click', function () {
                        DeviceMotionEvent.requestPermission()
                            .then(function (permissionState) {
                                if (permissionState === 'granted') {
                                    btn.style.display = 'none';
                                    window.addEventListener('devicemotion', handleMotionEvent, true);
                                } else {
                                    alert('未授予传感器权限，无法获取加速度信息。');
                                }
                            })
                            .catch(function (error) {
                                alert('请求传感器权限时出错：' + error);
                            });
                    });
                } else {
                    window.addEventListener('devicemotion', handleMotionEvent, true);
                }
            }

            const lowPassFilterAcc = createLowPassFilter(0.3);

            function handleMotionEvent(event) {
                let acc = event.acceleration;
                if (acc) {
                    let forwardAcc = calcForwardAcceleration(acc.y, acc.z);
                    let filteredAcc = lowPassFilterAcc(forwardAcc);
                    let speed = calcSpeed(filteredAcc);

                    chartA.pushData(forwardAcc);
                    chartB.pushData(speed);

                    // document.getElementById('acceleration').innerText =
                    //     "加速度 X: " + acc.x.toFixed(2) +
                    //     ", Y: " + acc.y.toFixed(2) +
                    //     ", Z: " + acc.z.toFixed(2) +
                    //     "\n估算向前加速度 " + forwardAcc.toFixed(2) +
                    //     "\n估算速度 " + converSpeedMps2Kmph(speed).toFixed(2);
                    document.getElementById('acceleration').innerText =
                        "IMU " + converSpeedMps2Kmph(speed).toFixed(0);
                } else {
                    document.getElementById('acceleration').innerText = "无法获取加速度信息。";
                }
            }

            function calcForwardAcceleration(accY, accZ) {
                const totalAcc = Math.sqrt(accY * accY + accZ * accZ);
                return accY < 0 ? totalAcc : -totalAcc;
            }

            let calcSpeed = function () {
                let _time = undefined,
                    _speed = 0,
                    _lastAcc = 0;
                return (acc, gpsSpeed = undefined) =>
                    _speed = gpsSpeed !== undefined ? gpsSpeed :
                        _speed + (_lastAcc + (_lastAcc = acc)) / 2 * (-(_time ?? Date.now()) + (_time = Date.now())) / 1000;
                ;
            }();

            let speedCorrectionTime = 0;
            function gpsSuccessCallback(position) {
                // document.getElementById('gps').innerText =
                //     "GPS位置 纬度: " + position.coords.latitude.toFixed(6) +
                //     ", 经度: " + position.coords.longitude.toFixed(6) +
                //     ", 速度: " + converSpeedMps2Kmph(position.coords.speed ?? 0).toFixed(1);
                document.getElementById('gps').innerText =
                    "GPS " + converSpeedMps2Kmph(position.coords.speed ?? 0).toFixed(0);

                chartC.pushData(position.coords.speed ?? 0);

                if (Date.now() - speedCorrectionTime > 15000) {
                    speedCorrectionTime = Date.now();
                    calcSpeed(0, position.coords.speed ?? 0);
                }
            }

            function gpsErrorCallback(error) {
                document.getElementById('gps').innerText =
                    "无法获取GPS位置。错误代码: " + error.code + " - " + error.message;
            }

            function converSpeedMps2Kmph(metersPerSecond) {
                const kilometersPerHour = metersPerSecond * 3.6;
                return kilometersPerHour;
            }

            function createLowPassFilter(alpha) {
                let previousValue = 0;
                return function (input) {
                    previousValue = previousValue + alpha * (input - previousValue);
                    return previousValue;
                }
            }
        }
        main();
    </script>

</body>